class_name SceneSystem
extends BaseSystem

## 场景管理器
## 抄的这个插件：https://github.com/maktoobgar/scene_manager
## 功能基本一样，可以参考下手册

class Options:
	var fade_speed:float = 1
	var fade_pattern:String = "fade"
	var smoothness:float = 0.1
	var inverted:bool = false
	var custom_texture:String = ""
class GeneralOptions:
	var color:Color = Color(0,0,0)
	var timeout:float = 0
	var clickable:bool = true
	var add_to_back:bool = true
#region 属性
const FADE:String = "fade"
const COLOR:String = "color"
const NO_COLOR:String = "no_color"
const BLACK:Color = Color(0,0,0)
const LOADING:String = "Loading"
const LOADING_AUTO:String = "LoadingAuto"
@export var scene_infos:Array[SceneInfo]
@onready var fade: ColorRect = %fade
@onready var animation_player: AnimationPlayer = %AnimationPlayer
var scene_info_dict:Dictionary = {}
var in_transition:bool = false
var stack:Array = []
var stack_limit:int = -1
var current_scene_id:String = ""
var first_time:bool = true
var patterns:Dictionary = {}
var load_scene:String = ""
var load_progress:Array = []
var recorded_scene_id:String = ""
@export_group("Loading")
@export var loading_fade_out_speed: float = 1.0
@export var loading_fade_in_speed: float = 1.0
@export var loading_fade_out_pattern: String = "fade"
@export var loading_fade_in_pattern: String = "fade"
@export var loading_fade_out_smoothness = 0.1 # (float, 0, 1)
@export var loading_fade_in_smoothness = 0.1 # (float, 0, 1)
@export var loading_fade_out_inverted: bool = false
@export var loading_fade_in_inverted: bool = false
@export var loading_color: Color = Color(0, 0, 0)
@export var loading_timeout: float = 0.0
@export var loading_clickable: bool = false
@export var loading_add_to_back: bool = true
@onready var fade_out_options = create_options(loading_fade_out_speed, loading_fade_out_pattern, loading_fade_out_smoothness, loading_fade_out_inverted)
@onready var fade_in_options = create_options(loading_fade_in_speed, loading_fade_in_pattern, loading_fade_in_smoothness, loading_fade_in_inverted)
@onready var general_options = create_general_options(loading_color, loading_timeout, loading_clickable, loading_add_to_back)
@export_group("")
#endregion
#region 字段
#endregion
#region 信号
signal load_finished
signal load_percent_changed(value:int)
signal scene_changed
signal fade_in_started
signal fade_out_started
signal fade_in_finished
signal fade_out_finished
#endregion

#region 基类方法
func init_system(args = null):
	super(args)
	set_process(false)
	_init_scene_info_dict()
	_set_current_scene()
	_get_patterns()
#endregion
#region 公共方法
## 设置回退Stack的容量
func set_back_limit(input:int):
	stack_limit = input
	if input == 0:
		stack.clear()
	elif input > 0:
		while stack.size() > stack_limit:
			stack.pop_front()
## 重置SceneSystem
func reset_scene_system():
	_set_current_scene()
	stack.clear()
## 创建Options
func create_options(fade_speed:float = 1.0,fade_pattern:String = "fade",smoothness:float = 0.1,inverted:bool = false,custom_texture:String = "") -> Options:
	var options:Options = Options.new()
	options.fade_speed = fade_speed
	options.fade_pattern = fade_pattern
	options.smoothness = smoothness
	options.inverted = inverted
	options.custom_texture = custom_texture
	return options
## 创建GeneralOptions
func create_general_options(color:Color = Color(0,0,0),timeout:float = 0.0,clickable:bool = true,add_to_back:bool = true) -> GeneralOptions:
	var options:GeneralOptions = GeneralOptions.new()
	options.color = color
	options.timeout = timeout
	options.clickable = clickable
	options.add_to_back = add_to_back
	return options
# 为游戏第一个加载的场景添加淡入转换
func show_first_scene(fade_in_options:Options,general_options:GeneralOptions):
	if first_time:
		first_time = false
		_set_in_transition(true)
		_set_clickable(general_options.clickable)
		_set_pattern(fade_in_options,general_options)
		if _timeout(general_options.timeout):
			await get_tree().create_timer(general_options.timeout).timeout
		if _fade_in(fade_in_options.fade_speed):
			await animation_player.animation_finished
			fade_in_finished.emit()
		_set_clickable(true)
		_set_in_transition(false)
## 非切换场景的淡入转换
func show_scene(fade_in_options:Options,general_options:GeneralOptions):
	_set_in_transition(true)
	_set_clickable(general_options.clickable)
	_set_pattern(fade_in_options,general_options)
	if _timeout(general_options.timeout):
		await get_tree().create_timer(general_options.timeout).timeout
	if _fade_in(fade_in_options.fade_speed):
		await animation_player.animation_finished
		fade_in_finished.emit()
	_set_clickable(true)
	_set_in_transition(false)
## 创建场景实例
func create_scene_instance(scene_id:String,use_sub_threads:bool = false) -> Node:
	return get_scene(scene_id,use_sub_threads).instantiate()
## 得到场景资源
func get_scene(scene_id:String,use_sub_threads:bool = false) -> PackedScene:
	var scene_info:SceneInfo = _get_scene_info_by_id(scene_id)
	if not scene_info:
		push_error("场景id{0}不存在".format([scene_id]))
		return null
	else:
		if scene_info.scene:
			return scene_info.scene
		elif not scene_info.scene_file_path.is_empty():
			ResourceLoader.load_threaded_request(scene_info.scene_file_path,"",use_sub_threads,ResourceLoader.CACHE_MODE_REUSE)
			return ResourceLoader.load_threaded_get(scene_info.scene_file_path)
		else:
			return null
## 切换场景
func change_scene(scene,fade_out_options:Options,fade_in_options:Options,general_options:GeneralOptions):
	if scene is PackedScene or scene is Node or (typeof(scene) == TYPE_STRING and not in_transition):
		first_time = false
		_set_in_transition(true)
		_set_clickable(general_options.clickable)
		_set_pattern(fade_out_options,general_options)
		if _fade_out(fade_out_options.fade_speed):
			await animation_player.animation_finished
			fade_out_finished.emit()
		if _change_scene(scene,general_options.add_to_back):
			if not (scene is Node or scene is PackedScene):
				# TODO 可能存在加入的节点并不是等待生成的节点吗？
				await get_tree().node_added
			scene_changed.emit()
		if _timeout(general_options.timeout):
			await get_tree().create_timer(general_options.timeout).timeout
		animation_player.play(NO_COLOR,-1,1,false)
		_set_pattern(fade_in_options,general_options)
		if _fade_in(fade_in_options.fade_speed):
			await animation_player.animation_finished
			fade_in_finished.emit()
		_set_clickable(true)
		_set_in_transition(false)
## 无转换效果的切换场景
func change_scene_no_effect(scene,hold_timeout:float = 0.0,add_to_back:bool = true):
	if scene is PackedScene or scene is Node or (typeof(scene) == TYPE_STRING and not in_transition):
		first_time = true
		_set_in_transition(true)
		await get_tree().create_timer(hold_timeout).timeout
		if _change_scene(scene,add_to_back):
			if scene is not Node:
				# TODO 可能存在加入的节点并不是等待生成的节点吗？
				await get_tree().node_added
		_set_in_transition(false)
## 有Loading界面的切换场景
## 是否自动切换界面，即是否需要手动按键结束loading界面
func change_scene_has_loading(scene_id:String,is_auto:bool):
	set_recorded_scene_id(scene_id)
	change_scene(LOADING_AUTO if is_auto else LOADING, fade_out_options, fade_in_options, general_options)
## 把加载场景添加到场景树上
func add_loaded_scene_to_scene_tree():
	if not load_scene.is_empty():
		var scene_resource = ResourceLoader.load_threaded_get(load_scene) as PackedScene
		if scene_resource:
			var scene = scene_resource.instantiate()
			scene.scene_file_path = load_scene
			var root = get_tree().root
			root.add_child(scene)
			root.move_child(scene,root.get_child_count() - 2)
			load_scene = ""
## 当您通过add_loaded_scene_to_scene_tree函数将加载的场景添加到场景树中时，在确定添加到场景树中的场景已完全准备好并可以更改活动场景后，可以调用此函数
func change_scene_to_existing_scene_in_scene_tree(fade_out_options:Options,fade_in_options:Options,general_options:GeneralOptions):
	_set_in_transition(true)
	_set_clickable(general_options.clickable)
	_set_pattern(fade_out_options,general_options)
	if _fade_out(fade_out_options.fade_speed):
		await animation_player.animation_finished
		fade_out_finished.emit()
	var root = get_tree().root
	root.get_child(root.get_child_count() - 1).free()
	var scene = root.get_child(root.get_child_count() - 1)
	get_tree().current_scene = scene
	var scene_id = _get_scene_id_by_scene_file_path(scene.scene_file_path)
	if general_options.add_to_back and not scene_id.is_empty():
		_append_stack(scene_id)
	if _timeout(general_options.timeout):
		await get_tree().create_timer(general_options.timeout).timeout
	animation_player.play(NO_COLOR,-1,1,false)
	_set_pattern(fade_in_options,general_options)
	if _fade_in(fade_in_options.fade_speed):
		await animation_player.animation_finished
		fade_in_finished.emit()
	_set_clickable(true)
	_set_in_transition(false)
## 交互式加载场景
func load_scene_interactive(scene_id:String,use_sub_threads = false):
	var scene_info:SceneInfo = _get_scene_info_by_id(scene_id)
	if not scene_info:
		push_error("场景id{0}不存在".format([scene_id]))
		return
	elif not scene_info.scene_file_path.is_empty():
		set_process(true)
		load_scene = scene_info.scene_file_path
		ResourceLoader.load_threaded_request(load_scene,"",use_sub_threads,ResourceLoader.CACHE_MODE_IGNORE)
## 得到加载场景
func get_load_scene() -> PackedScene:
	if not load_scene.is_empty():
		return ResourceLoader.load_threaded_get(load_scene) as PackedScene
	return null
## 切换场景到刚加载的load_scene
func change_scene_to_loaded_scene(fade_out_options:Options,fade_in_options:Options,general_options:GeneralOptions):
	if not load_scene.is_empty():
		var scene = ResourceLoader.load_threaded_get(load_scene) as PackedScene
		if scene:
			change_scene(scene,fade_out_options,fade_in_options,general_options)
## 得到上一个scene_id
func get_previous_scene_id() -> String:
	return stack[len(stack) - 1]
## 得到idx索引的scene_id
func get_previous_scene_id_at(idx:int) -> String:
	if idx < len(stack):
		return stack[idx]
	return ""
## 弹出上一个scene_id
func pop_previous_scene_id() -> String:
	return _pop_stack()
## 得到先前被记录的场景数量
func get_previous_scene_ids_size() -> int:
	return stack.size()
## 设置记录场景id
func set_recorded_scene_id(scene_id:String):
	if _get_scene_info_by_id(scene_id):
		recorded_scene_id = scene_id
## 返回记录场景id
func get_recorded_scene_id() -> String:
	return recorded_scene_id
## 暂停（fade out），可以恢复
func pause(fade_out_options:Options,general_options:GeneralOptions):
	_set_in_transition(true)
	_set_clickable(general_options.clickable)
	_set_pattern(fade_out_options,general_options)
	if _fade_out(fade_out_options.fade_speed):
		await animation_player.animation_finished
		fade_out_finished.emit()
## 暂停后恢复（fade in）
func resume(fade_in_options:Options,general_options:GeneralOptions):
	_set_clickable(general_options.clickable)
	_set_pattern(fade_in_options,general_options)
	if _fade_in(fade_in_options.fade_speed):
		await animation_player.animation_finished
		fade_in_finished.emit()
	_set_in_transition(false)
	_set_clickable(true)
#endregion
#region 私有方法
## 根据scene_id得到场景信息
func _get_scene_info_by_id(scene_id:String) -> SceneInfo:
	if scene_info_dict.has(scene_id):
		return scene_info_dict[scene_id]
	return null
## 根据scene_file_path得到场景信息
func _get_scene_id_by_scene_file_path(scene_file_path:String) -> String:
	var res = scene_infos.filter(func(info:SceneInfo):return info.scene_file_path == scene_file_path)
	if res.size() > 0:
		return res[0].id
	return ""
## 设置当前scene_id
func _set_current_scene():
	var scene_id = _get_scene_id_by_scene_file_path(get_tree().current_scene.scene_file_path)
## 取得所有的模式
func _get_patterns():
	var dir_path:String = "res://textures/scene/shader_patterns/"
	var dir = DirAccess.open(dir_path)
	if dir:
		dir.list_dir_begin()
		while true:
			var file_folder:String = dir.get_next()
			if file_folder == "":
				break
			elif file_folder.get_extension() == "import":
				file_folder = file_folder.replace(".import","")
			if file_folder.get_extension() == "png":
				var key = file_folder.replace("."+file_folder.get_extension(),"")
				if key not in patterns.keys():
					patterns[key] = load(dir_path + file_folder)
		dir.list_dir_end()
## 将所有scene_info存入字典
func _init_scene_info_dict():
	for info in scene_infos:
		if not scene_info_dict.has(info.id):
			scene_info_dict[info.id] = info
## 淡入
func _fade_in(speed:float) -> bool:
	if speed == 0:
		return false
	fade_in_started.emit()
	animation_player.play(FADE,-1,1/speed,false)
	return true
## 淡出
func _fade_out(speed:float) -> bool:
	if speed == 0:
		return false
	fade_out_started.emit()
	animation_player.play(FADE,-1,-1/speed,true)
	return true
## 设置是否在转换中
func _set_in_transition(in_transition:bool):
	self.in_transition = in_transition
## 添加到Stack中
func _append_stack(scene_id:String):
	stack.append(current_scene_id)
	current_scene_id = scene_id
	if stack_limit > 0:
		while stack.size() > stack_limit:
			stack.pop_front()
## 弹出上一个scene_id
func _pop_stack() -> String:
	var scene_id = stack.pop_back()
	if scene_id:
		current_scene_id = scene_id
	return current_scene_id
## 切换到上一个场景
func _back() -> bool:
	var scene_id = _pop_stack()
	if scene_id:
		var scene_info = _get_scene_info_by_id(scene_id)
		if not scene_info:
			return false
		get_tree().change_scene_to_file(scene_info.scene)
		return true
	return false
## 重新切换到当前场景
func _refresh() -> bool:
	var scene_info = _get_scene_info_by_id(current_scene_id)
	if not scene_info:
		return false
	get_tree().change_scene_to_file(scene_info.scene)
	return true
## 切换场景
func _change_scene(scene,add_to_back:bool):
	if scene is PackedScene:
		scene.get_local_scene()
		var scene_instance = scene.instantiate()
		var root = get_tree().root
		get_tree().current_scene.free()
		root.add_child(scene_instance)
		get_tree().current_scene = scene_instance
		var scene_id = _get_scene_id_by_scene_file_path(scene.resource_path)
		scene_instance.scene_file_path = load_scene
		if add_to_back:
			_append_stack(scene_id)
		load_scene = ""
		return true
	elif scene == "back":
		return _back()
	elif scene == "null" or scene == "ignore" or scene == "":
		return false
	elif scene == "reload" or scene == "refresh" or scene == "restart":
		return _refresh()
	elif scene == "exit" or scene == "quit":
		get_tree().quit(0)
	else:
		var info:SceneInfo = _get_scene_info_by_id(scene)
		if not info:
			return false
		get_tree().change_scene_to_file(info.scene_file_path)
		if add_to_back:
			_append_stack(info.id)
		return true
	return false
## 设置能否点击
func _set_clickable(clickable:bool):
	if clickable:
		fade.mouse_filter = Control.MOUSE_FILTER_IGNORE
	else:
		fade.mouse_filter = Control.MOUSE_FILTER_STOP
## 延迟一段时间
func _timeout(timeout:float) -> bool:
	if timeout != 0:
		animation_player.play(COLOR,-1,1,false)
		return true
	return false
## 设置模式
func _set_pattern(options:Options,general_options:GeneralOptions):
	if options.fade_pattern not in patterns:
		options.fade_pattern = "fade"
	if options.fade_pattern == "fade":
		fade.material.set_shader_parameter("linear_fade",true)
		fade.material.set_shader_parameter("color", Vector3(general_options.color.r, general_options.color.g, general_options.color.b))
		fade.material.set_shader_parameter("custom_texture", null)
	else:
		fade.material.set_shader_parameter("linear_fade", false)
		fade.material.set_shader_parameter("custom_texture", patterns[options.fade_pattern])
		fade.material.set_shader_parameter("inverted", options.inverted)
		fade.material.set_shader_parameter("smoothness", options.smoothness)
		fade.material.set_shader_parameter("color", Vector3(general_options.color.r, general_options.color.g, general_options.color.b))
#endregion
#region 生命周期函数
func _process(delta: float) -> void:
	var prev_percent:int = 0
	if load_progress.size() != 0:
		prev_percent = int(load_progress[0] * 100)
	var status = ResourceLoader.load_threaded_get_status(load_scene,load_progress)
	var next_percent:int = int(load_progress[0] * 100)
	if prev_percent != next_percent:
		load_percent_changed.emit(next_percent)
	if status == ResourceLoader.THREAD_LOAD_LOADED:
		set_process(false)
		load_progress = []
		load_finished.emit()
	elif status == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
		pass
	else:
		push_error("场景资源加载失败了")
#endregion
